package main

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"
)

var NodeAddrs []string = make([]string, nodeNumber) // 存放系统启动的节点 ip:port
var IsAcceptMsg = make(map[string][]string)         // 防止网络风暴，存放系统中节点接受数据的缓存(每个节点的缓存最大为节点总数)
var IsAcceptMsgLock sync.Mutex

var NodeMapping = make(map[string][]string) // 存放节点之间映射关系
var NodeMappingLock sync.Mutex

func StartServer() {
	// 组织系统中存在的节点地址
	startNodeID, _ := strconv.Atoi(nodeStartNum)

	for i := 0; i < nodeNumber; i++ {
		nodeAddr := "localhost:" + strconv.Itoa(startNodeID+i)
		//NodeAddrs = append(NodeAddrs, nodeAddr)
		NodeAddrs[i] = nodeAddr
	}

	// 网络结构
	for index, nodeAddr := range NodeAddrs {
		if index == 0 {
			continue
		}

		if index%maxConn == 0 {
			parentNode := NodeAddrs[index-1-(index-1)%maxConn] // 拿到上一轮的parentNode数目
			NodeMapping[parentNode] = append(NodeMapping[parentNode], nodeAddr)

			NodeMapping[nodeAddr] = append(NodeMapping[nodeAddr], parentNode)
			continue
		}

		// 求该节点的链接节点
		parentNode := NodeAddrs[index-index%maxConn]
		NodeMapping[parentNode] = append(NodeMapping[parentNode], nodeAddr)
	}

	for _, nodeAddr := range NodeAddrs {
		// 如果该节点没有分配网络映射
		if _, ok := NodeMapping[nodeAddr]; !ok {
			// 拿到该节点在网络映射中对应的键值
			var key string // 节点的键值
			for i, item := range NodeMapping {
				for _, j := range item {
					if j == nodeAddr {
						key = i
						break
					}
				}
			}
			NodeMapping[nodeAddr] = []string{key}
		}
	}

	//fmt.Println(NodeMapping)  // 打印节点映射

	// TODO 添加内容，防止重复打开钱包
	from, _ := GetNodeKey("10001")
	to, _ := GetNodeKey("10002")

	fromPerson = from
	toPerson = to

	// 开启协程
	for _, item := range NodeAddrs {
		go server(item)
	}
}

func server(nodeAddr string) {
	// 创建服务端
	listener, err := net.Listen(protocol, nodeAddr)

	fmt.Printf("端口节点 < %s > 已启动...\n", nodeAddr)

	if err != nil {
		fmt.Println("服务器 listen err: ", err)
	}
	defer listener.Close()

	for {
		// 等待客户端连接请求
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("accept err:", err)
			return
		}

		// 处理用户请求，新建一个协程
		go HandleMulServerConn(conn, nodeAddr)
	}
}

// 处理发来的请求，实现多个服务端之间通讯
func HandleMulServerConn(conn net.Conn, nodeAddr string) {
	// 函数调用完毕，自动关闭conn
	defer conn.Close()

	buf := make([]byte, 30000000) // TODO 满足1200

	n, err := conn.Read(buf) // 读取用户数据

	if err != nil {
		fmt.Println("read err:", err)
		return
	}

	IsAcceptMsgLock.Lock()
	isAcceptMsg := IsAcceptMsg[nodeAddr]
	IsAcceptMsgLock.Unlock()
	// 判断收到的数据是否在自己的缓存中，如果在，直接退出; 如果不在，广播给其他节点
	for _, item := range isAcceptMsg {
		// 如果在，直接退出
		if item == string(buf[:n]) {
			return
		}
	}

	// a 获取该节点对应的区块链对象
	nodeID := strings.Split(nodeAddr, ":")[1]

	currentNodeChainObjLock.Lock() // 加锁
	blockChainObj := currentNodeChainObj[nodeID]
	currentNodeChainObjLock.Unlock() // 解锁

	// b 执行解析命令对数据进行解析
	if string(buf[0]) == "d" {
		// 命令行信息
		go blockChainObj.ParseCMDData(buf[:n]) // 解析命令行数据
		return                                 // (不进行广播)
	} else {
		// 交易 或 微块信息(广播)
		go blockChainObj.ParseData(buf[:n]) // 解析交易、微块数据
	}

	IsAcceptMsgLock.Lock() // 原因：读取的时候，其它协程可能正在写
	// 设置缓存最大长度
	if len(IsAcceptMsg[nodeAddr]) > nodeNumber*2 {
		// 清空该节点缓存
		delete(IsAcceptMsg, nodeAddr)
	}

	IsAcceptMsg[nodeAddr] = append(IsAcceptMsg[nodeAddr], string(buf[:n]))
	IsAcceptMsgLock.Unlock() // 解锁

	// 模拟延迟
	time.Sleep(delay * time.Millisecond) // 设置延迟
	// 将收到的数据广播给连接的8个节点(循环发送)

	NodeMappingLock.Lock()
	nodeMapping := NodeMapping[nodeAddr]
	NodeMappingLock.Unlock()
	for _, item := range nodeMapping {
		go sendMessage(item, buf[:n]) // 并发发送消息
	}
}

// 以并发的方式建立链接发送数据
func sendMessage(item string, buf []byte) {
	// 建立客户端连接其他节点并发送，广播该该节点收到的数据
	clientConn, err := net.Dial(protocol, item)

	if err != nil {
		fmt.Println("clientConn err:", item)
		return
	}

	defer clientConn.Close()

	_, err = clientConn.Write(buf)

	if err != nil {
		fmt.Println("Write data err:", err)
		return
	}
}
